package de.rj.urwalking.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Timer;
import java.util.TimerTask;

import org.json.JSONObject;

import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import de.rj.urwalking.config.Config;

/**
 * 
 * @author Richard Schaller
 * @author adapted by mka
 *
 */
public class FileUpload {

	private static final String TAG = Config.UPLOADTAG;
	private static final String SERVER = Config.FILE_UPLOAD_SERVER;
	private static final int SENDING_INTERVAL = Config.FILE_UPLOAD_INTERVAL_STANDARD;
	private static final int SENDING_INTERVAL_FAILURE = Config.FILE_UPLOAD_INTERVAL_FAILURE;
	private static final String PHP_SCRIPT = Config.FILE_UPLOAD_PHP;

	private boolean blnRunning = false;
	
	private final Timer timer = new Timer();
	
	private int intZeroCnt = 0;
	
	private static FileUpload localinstance = null;
	
	private Handler handler = new Handler();
	private Handler handlerSend = new Handler();

	private Runnable fileUploadTaskRunnable = new Runnable() {
		public void run() {
			new FileUploadTask().execute();
		}
	};
	
	private Runnable sendMessageRunnable;
	
	public FileUpload(){
		
	}
	
	public void send(String server, String msg, long ts, int pid){
		final String[] ar = new String[4];
		ar[0] = server;
		ar[1] = msg;
		ar[2] = Long.toString(ts);
		ar[3] = Integer.toString(pid);
		sendMessageRunnable = new Runnable(){
				public void run(){
					new MessageSender().execute(ar);
				}
			};
		Log.i(TAG, "Try to send message "+msg+" to server "+server);
		handlerSend.postAtFrontOfQueue(sendMessageRunnable);				
	}
	
	private void setBlnRunning(boolean b){
		this.blnRunning = b;
	}
	
	private boolean getBlnRunning(){
		return this.blnRunning;
	}
	
	
	public static FileUpload getInstance() {
		if(localinstance == null) {
			localinstance = new FileUpload();
		}
		return localinstance;
	}
	
	
	public synchronized void start(){
		intZeroCnt=0;
		setBlnRunning(true);
		handler.postAtFrontOfQueue(fileUploadTaskRunnable);		
	}
	
	class MessageSender extends AsyncTask<String, Void, Boolean>{
		@Override
		protected Boolean doInBackground(String... params){
			boolean blnRes = false;
			if(!isCancelled()){
				String resp = null;
				try{
					 resp = FileUploadUtil.sendMessage(params[0], params[1], params[2], params[3]);
					 Log.i(TAG, "Answer of server given on post of" + params[1] +" was: "+resp);
					 if(resp!=null)
						 blnRes=true;
					 else
						 blnRes=false;
						if(params[1].equals("cancel")){
							//bei cancel muss auch der Upload sterben
							Log.i(TAG, "Kill the file upload task, because of cancel.");
							handler.removeCallbacks(fileUploadTaskRunnable);
						}					 
				}catch(IOException io){ 
					 Log.e(TAG, "IOException trying to send" +params[1]+ " experiment to server.");
					 blnRes=false;
				}catch(Exception e){
					Log.e(TAG, "Error attempting to send"+params[1]+ "experiment to server. "+e.toString());
					blnRes=false;
				}finally{

				}
				
			}
			return blnRes;
		}
		
		@Override
		protected void onPostExecute(Boolean bSuccess){
			if(bSuccess){
				this.cancel(true);
				Log.i(TAG, "Cancel send message task due to success.");
			}
			else{
				if(!isCancelled()){
				Log.e(TAG, "I will retry to send message due to failure of send.");
				handlerSend.postDelayed(sendMessageRunnable,						
						SENDING_INTERVAL_FAILURE);
				}
			}
		}
		
	}
	
	class FileUploadTask extends AsyncTask<Void, Void, Boolean> {
		@Override
		protected Boolean doInBackground(Void... params) {
			if(!isCancelled()){
			Log.i(TAG, "Try to send files to server.");

			try {
				File[] files = getSendableFiles();

				if (files == null) {
					return false;
				} else if (files.length==0){
					intZeroCnt++;
					//true zurueckgeben, um retry in Config.FILE_UPLOAD_INTERVAL_STANDARD ms zu erzwingen!!!
					//ACHTUNG: voellig kontraintuitiv!!
					//=> geaendert nach false
					return false;					
				}


				for (File file : files) {
					if (file.length() == 0) {
						Log.i(TAG, "Try to delete file " + file.getAbsolutePath()
								+ " due to size 0.");
						boolean blnDeleted = file.delete();
						if (blnDeleted)
							Log.i(TAG, "Successfully deleted file of size 0.");
						else
							Log.e(TAG, "Failed to delete file of size 0.");
						continue;
					}

					Log.i(TAG, "Uploading file '" + file.getAbsolutePath()
							+ "'");
					String response=null;
					try {
						response = FileUploadUtil.uploadFile(SERVER
								+ PHP_SCRIPT, file.getAbsolutePath());

					}catch(UnknownHostException uhe){
						Log.e(TAG, "Unknown server "+SERVER);
						//false zurueckgeben, um retry zu erzwingen
						return false;
					}catch (Exception e) {
						e.printStackTrace();
						Log.e(TAG,
								"An exception occurred in FileUploadUtil.uploadFile: "+e.toString());
						intZeroCnt=0;
						//false zurueckgeben, um retry zu erzwingen
						return false;
					}

					// encapsulate response in a JSONObject to allow for various
					// key-value-pairs as answers
					if(response==null){
						return false;
					}else{
						final JSONObject obj = new JSONObject(response);
						final String status = obj.getString("status");					
							if (status.equals("OK")) {
									Log.i(TAG, "Upload successful. Try to delete file "
											+ file.getAbsolutePath());
							} else if(status.equals("RESEND")){
								Log.e(TAG, "Server requested RESEND of file " + file.getAbsolutePath());
								//false zurueckgeben, um retry zu erzwingen -> wirklich??????										
								return false;								
							}else{
								Log.e(TAG, "Failed to upload file " + file.getAbsolutePath()+" SERVER answered: "+response);
							}
							
					boolean blnDeleted = file.delete();
					if (blnDeleted)
						Log.i(TAG,
								"Successfully deleted file "
										+ file.getAbsolutePath());
					else
						Log.e(TAG,
								"Failed to delete file "
										+ file.getAbsolutePath());
					}
					
				}
				
				intZeroCnt=0;
				//keep watching directory continously.	
				return true;	

			} catch (Exception e) {
				Log.e(TAG, FileUpload.class.getName()
						+ ": An exception occurred in method doInBackground: "+e.toString());
				//false zurueckgeben, um retry zu erzwingen				
				return false;
			}
			//else von !isCancelled()
			}else{
				Log.i(TAG, "Stop asyncTask: "+FileUploadTask.class.getName());
				return false;
			}
		}

		@Override
		// wird "automatisch" nach doInBackground aufgerufen; der
		// Uebergabeparameter
		// ist das return value von doInBackground
		protected void onPostExecute(Boolean bSuccess) {
		//falls ein Experiment laeuft:
			if(ExperimentManager.getInstance().isExperimentRunning()){
				if(bSuccess){
					Log.i(TAG,
							"I will again check for files to upload in "
									+ Integer.toString(SENDING_INTERVAL) + " ms");
					handler.postDelayed(fileUploadTaskRunnable,
							SENDING_INTERVAL);
					setBlnRunning(true);	
				}else{
					Log.i(TAG,
							"Last time I tried to upload files, something went wrong. I will retry to upload files in "
									+ Integer.toString(SENDING_INTERVAL_FAILURE)
									+ " ms");
					handler.postDelayed(fileUploadTaskRunnable,
							SENDING_INTERVAL_FAILURE);
					setBlnRunning(true);
				}
			}
			//falls kein Experiment laeuft: jetzt kommen nebenbedingungen ins Spiel
			else{
				if(intZeroCnt<Config.MAXIMUM_TRIALS_ZERO_FILES-1){
					Log.i(TAG,
							"I will again check for files to upload in "
									+ Integer.toString(SENDING_INTERVAL) + " ms");
					handler.postDelayed(fileUploadTaskRunnable,
							SENDING_INTERVAL);
					setBlnRunning(true);				
				}else{
					this.cancel(true);					
					Log.i(TAG, "As "+ (intZeroCnt+1) +" >= "+ Config.MAXIMUM_TRIALS_ZERO_FILES +" I have to cancel the upload task. Task is cancelled == " + this.isCancelled());
					setBlnRunning(false);
					//reset counter
					intZeroCnt=0;
				}
			}
	}
		}
	
	public boolean isFileUploadTaskRunning(){
		return getBlnRunning();
	}

	/**
	 * 
	 * @return
	 * @throws IOException
	 */

	public synchronized File[] getSendableFiles() throws IOException {
		Log.i(TAG,"Try to get all sendable files");
		File dirFiles = new File(Environment.getExternalStorageDirectory(),
				Config.FOLDER_NAME);
		
		File[] allSendableFiles = dirFiles.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				// files only
				if (pathname.isFile() == false) {
					return false;
					// make sure, the csv sensordata of current logfile is not
					// sent
				} else if (ExperimentManager.getInstance()
						.isExperimentRunning()
						&& pathname.getName().equals(
								ExperimentManager.getInstance()
										.getCurrentExperiment()
										.generateFilename())) {
					return false;
				}
					return ((pathname.getName().startsWith("log-")
						&& pathname.getName().endsWith("-sensordata.csv")) || (pathname
						.getName().startsWith("experimentdata-") && pathname
						.getName().endsWith(".xml")||(pathname.getName().startsWith("registrationdata-")&&pathname.getName().endsWith(".xml"))));
			}
		});
		Log.i(TAG, "Found "+allSendableFiles.length+" files to be sent");
		allSendableFiles = sortFilesDescending(allSendableFiles);
		return allSendableFiles;
	}
	
	/*
	 * 
	 */	
	private File[] sortFilesDescending(File[] files){
		 Arrays.sort(files, new Comparator<File>() {
		      public int compare(final File f1, final File f2) {
		          String s1 = ((File) f1).getName().toLowerCase();
		          String s2 = ((File) f2).getName().toLowerCase();
		          
		          final int s1Dash = s1.indexOf('-');
		          final int s2Dash = s2.indexOf('-');
		          
		          s1 = s1.substring(0,s1Dash);
		          s2 = s2.substring(0,s2Dash);
		          
		          /*
		           * r - l => -1 / r - e => -1 / r - r => -1
		           * l - e => 1 / l - r => 1 /  l - l => -1
		           * e - l => -1 /  e - r => 1 /  e - e => -1
		           */
		          if(s1.equals(s2))
		        	  return -1;
		          else
		        	  if((s1.equals("registrationdata")&&s2.equals("experimentdata"))
		        			  || (s1.equals("registrationdata")&&s2.equals("log"))
		        			  || (s1.equals("experimentdata")&&(s2.equals("log")))	        			 
		        			)	        			 	        		  
		        	  	return -1;
		        	  else
		        		return 1;	         
		      }
		    });
	    return files;
	}
	
	
	public synchronized boolean removeFiles(long ts, int pid){
		boolean blnRes = false;
		Log.i(TAG,"Try to get all files to be removed.");
		final long tst = ts;
		final int partid = pid;
		File dirFiles = new File(Environment.getExternalStorageDirectory(),
				Config.FOLDER_NAME);
		
		File[] all2bdeleted = dirFiles.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				// files only
				if (pathname.isFile() == false) {
					return false;
					// make sure, the csv sensordata of current logfile is not
					// sent
				}
					return ((pathname.getName().startsWith("log-"+Long.toString(tst)+"-"+Integer.toString(partid))
						&& pathname.getName().endsWith("-sensordata.csv")) || (pathname
						.getName().startsWith("experimentdata-"+Long.toString(tst)+"-"+Integer.toString(partid)) && pathname
						.getName().endsWith(".xml")));
			}
		});		
		
		if(all2bdeleted==null){
			Log.e(TAG, "Filter went wrong in removeFiles of class "+FileUpload.class.getName());
			blnRes=false;
		}else if(all2bdeleted.length==0){
			Log.i(TAG, "Found 0 files to be deleted.");
			blnRes=false;
		}else{
			Log.i(TAG, "Found "+all2bdeleted.length+" files to be removed.");
			for(File file : all2bdeleted){
				boolean blnDeleted = file.delete();
				if (blnDeleted){
					Log.i(TAG, "Successfully deleted file "+file.getName());
					blnRes=true;
				}else{
					Log.e(TAG, "Failed to delete file "+file.getName());
					blnRes=false;
				}
			}
		}
		
		return blnRes;
	}
	
	public synchronized void finish() {
		Log.d(TAG, "Finish file upload task.");		
		
		try {
			timer.schedule(new TimerTask() {
			    public void run() {					
					
					try {
						handler.removeCallbacks(fileUploadTaskRunnable);
						handler.postAtFrontOfQueue(fileUploadTaskRunnable);
						setBlnRunning(false);
					} catch(Exception e) {
						Log.e(TAG, "An exception occurred in method finish of class "+FileUpload.class.getName());
					}
			    }
			}, 500);
			
			Thread.sleep(500);
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	}
